import util.VectorUtils;
import java.util.*;

public class Point
{
	public enum DIST_TYPE
	{
		EUCLIDEAN,
		COS_SIM
	};

	private int _ID;
	private Double _featureDense[];
	private Map<Object, Double> _featureSparse;
	private boolean _dense;
	private DIST_TYPE _type;

	/**
	 * Without setting the ID (default is -1)
	 * @param dimension
	 * @param dense a boolean defining whether a point is sparse or dense
	 * @param type the variable defining distance metric used
	 */
	public Point(int dimension, boolean dense, DIST_TYPE type)
	{
		_ID = -1;
		_featureDense = new Double[dimension];
		for (int i = 0; i < dimension; i++) _featureDense[i] = 0d;
		_featureSparse = new HashMap<Object, Double>();
		_type = type;
		_dense = dense;
	}

	/**
	 * Constructor for dense point
	 * @param ID
	 * @param feature array of dense feature vector of the point
	 * @param type the variable defining distance metric used
	 */
	public Point(int ID, Double feature[], DIST_TYPE type)
	{
		_ID = ID;
		_featureDense = feature;
		_featureSparse = null;
		_dense = true;
		this._type = type;
	}

	/**
	 * Constructor for sparse point
	 * @param ID
	 * @param feature array of sparse feature vector of the point
	 * @param type the variable defining distance metric used
	 */
	public Point(int ID, Map<Object, Double> feature, DIST_TYPE type)
	{
		_ID = ID;
		_featureSparse = feature;
		_featureDense = null;
		_dense = false;
		this._type = type;
	}

	public int ID() {return _ID;}
	public Double[] FeatureDense() {return _featureDense;}
	public Map<Object, Double> FeatureSparse() {return _featureSparse;}
	public boolean isDense() {return _dense;}
	public DIST_TYPE type() {return _type;}

	/**
	 * 
	 * @param m1 dense feature vector of point 1
	 * @param m2 dense feature vector of point 2
	 * @return the euclidean distance between point 1 and point 2
	 */
	private double denseEuclidean(Double[] m1, Double[] m2)
	{
		int len = m1.length;
		double retVal = 0.0;
		for (int i = 0; i < len; i++) retVal += Math.pow(m1[i] - m2[i], 2);
		return Math.sqrt(retVal);
	}

	/**
	 * 
	 * @param m1 sparse feature vector of point 1
	 * @param m2 sparse feature vector of point 2
	 * @return the euclidean distance between point 1 and point 2
	 */
	private double sparseEuclidean(Map<Object, Double> m1, Map<Object, Double> m2)
	{
		double retVal = 0d;
		Set<Object> all_keys = new HashSet<Object>();
	        all_keys.addAll(m1.keySet());
	        all_keys.addAll(m2.keySet());
	        for (Object key : all_keys) {
	                Double d1 = m1.get(key);
	                Double d2 = m2.get(key);
	                if (d1 == null)
	                        retVal += Math.pow(d2, 2);
	                else if (d2 == null)
	                        retVal += Math.pow(d1, 2);
	                else
	                        retVal += Math.pow(d1 - d2, 2);
	        }
		return Math.sqrt(retVal);
	}

	/**
	 * Measures the distance between point and a target point. The metric is defined in the constructor
	 * @param target another point
	 * @return the distance to target point
	 */
	public double Distance(Point target)
	{
		double retVal = 0;
		switch (_type)
		{
			case EUCLIDEAN : 
				if (_dense) retVal = denseEuclidean(_featureDense, target.FeatureDense());
				else retVal = sparseEuclidean(_featureSparse, target.FeatureSparse());
				break;
			case COS_SIM :
				// no need to implement cosine similarity for dense vector for now
				if (_dense) retVal = 0;
				else retVal = (1d + (-1d * VectorUtils.CosSim(_featureSparse, target.FeatureSparse())));
				break;
		}
		return retVal;
	}

	/**
	 * Used to get the string representation of the point
	 */
	public String toString()
	{
		StringBuilder sb = new StringBuilder();
		if (!_dense) 
		{
			for (Object key : _featureSparse.keySet())
			{
				sb.append("[" + key + ":" + _featureSparse.get(key) + "] ");
			}	
		}
		else
		{
			int dimension = _featureDense.length;
	//		sb.append("ID : " + _ID + ", dimension : " + dimension + "\r\n");
			//sb.append("[ ");
			sb.append(_featureDense[0]);
			for (int i = 1 ; i < dimension; i++)
			{
	//			sb.append(String.format("%1$.2f ", _featureDense[i]));
				sb.append(" " + _featureDense[i]);
			}
		}
		//sb.append("]");
		return sb.toString();
	}

}
